<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue入门</title>
    <style>
      .basic {
        width: 100px;
        height: 100px;
        border: 1px solid black;
      }
      .bgred {
        background-color: red;
      }
      .bgblue {
        background-color: blue;
      }
      .bggreen {
        background-color: green;
      }
      .addstyle1 {
        font-style: italic;
      }
      .addstyle2 {
        font-weight: 700;
      }
      .addstyle3 {
        border-radius: 5px;
      }
    </style>
  </head>
  <body>
    <div id="root">
      <!-- 插值语法使用 -->
      <h1>{{ message }}</h1>
      <h2>姓名：{{ person.name }} ID：{{ person.id }}</h2>
      <h3>现居城市：{{ city[4] }}</h3>
      <h4>
        北上广深：
        <i>{{ city[0] }}</i> <i>{{ city[1] }}</i> <i>{{ city[2] }}</i>
        <i>{{ city[3] }}</i>
      </h4>
      <hr />

      <!-- 事件 -->
      <button @click="addId">让ID加1</button>
      <hr />

      <!-- 计算属性 -->
      姓:
      <input type="text" v-model="firstName" />
      名：
      <input type="text" v-model="lastName" />
      全名:
      <span>{{fullName}}</span>
      <hr />

      <!-- 绑定样式:
              1.class样式：
                  写法 :class='xxx' xxx可以是字符串、对象、数组
                    字符串写法,适用于样式的类名不确定，需要动态指定
                    对象写法，适用于样式个数确定，类名确定，但需要动态指定
                    数组写法，适用于样式的个数不确定，类名也不确定
              2.style样式：
                  对象写法 :style='{fontSize:xxx}' xxx动态指定
                  数组写法 :style='[a,b]' a、b是样式对象 -->
      <!-- class样式 字符串写法,适用于样式的类名不确定，需要动态指定 -->
      <div class="basic" :class="mood" @click="changeMood">{{message}}</div>
      <br />
      <!-- class样式 数组写法，适用于样式的个数不确定，类名也不确定 -->
      <div class="basic" :class="classArr">{{message}}</div>
      <br />
      <!-- class样式 对象写法，适用于样式个数确定，类名确定，但需要动态指定 -->
      <div class="basic" :class="classObj">{{message}}</div>
      <br />

      <!-- style样式 对象写法-->
      <div class="basic" :style="styleObj">{{message}}</div>
      <br />
      <!-- style样式 数组写法-->
      <div class="basic" :style="styleArr">{{message}}</div>
      <hr />

      <!-- Vue监视数据 -->
      <h2>Vue监视数据 学生信息</h2>
      <button @click="student.age++">年龄+1岁</button><br />
      <button @click="addSex">添加性别属性,默认值为男</button><br />
      <button @click="student.sex='秀吉'">修改性别</button><br />
      <button @click="addFriends">在列表头添加一个朋友</button><br />
      <button @click="updateFirstFriendName">
        修改第一个朋友的名字为：张三</button
      ><br />
      <button @click="addHobby">添加一个爱好</button><br />
      <button @click="updateHobby">修改第一个爱好为：开车</button><br />
      <button @click="removeBad">过滤爱好中的打豆豆</button><br />
      <h3>姓名:{{student.name}}</h3>
      <h3>年龄:{{student.age}}</h3>
      <h3>性别:{{student.sex}}</h3>
      <h3>爱好:</h3>
      <ul>
        <li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
      </ul>
      <h3>朋友们：</h3>
      <ul>
        <li v-for="(f,index) in student.friends" :key="index">
          {{f.name}}--{{f.age}}
        </li>
      </ul>
      <hr />

      <!-- 过滤器使用 -->
      <h2>显示格式化后的时间</h2>
      <h3>现在是{{time | timeFormater}}</h3>
      <!-- 传参 -->
      <h3>现在是{{time | timeFormater('YYYY_MM_DD')}}</h3>
      <!-- 使用全局过滤器 -->
      <h3>现在是{{time | timeFormater | mySlice}}年</h3>
      <hr />

      <!-- 自定义指令 -->
      <h2>自定义指令</h2>
      <h2>使用v-text绑定时myNum为：<span v-text="myNum"></span></h2>
      <h2>
        使用自定义指令v-big(数值放大10倍)绑定时myNum为：
        <span v-big="myNum"></span>
      </h2>
      <h2>自定义全局指令：<span v-two="myNum"></span></h2>
      <button @click="myNum++">点我myNum++</button>
      <br />
      <input type="text" v-focus-on:value="myNum" />

      <hr />

      <!-- 生命周期 -->
      <h2 :style="{opacity}">变变变</h2>
      <button @click="opacity=1">透明度设置为1</button>
      <button @click="destoryVm">是时候毁灭一切了</button>

      <!-- 条件渲染
           使用详情看v-if.html和v-show.html
            1.v-if
              写法：
                1)v-if='表达式'
                2)v-else-if='表达式'
                3)v-else='表达式'
              适用于切换频率低的场景
              特点：不展示的DOM元素直接移除
              注：v-if可以和v-else-if、v-else一起使用，但要求结构不能被其他DOM元素中断
            2.v-show
              写法：v-show='表达式'
              适用于切换频率高的场景
              特点：不展示的DOM元素不被移除，仅仅是使用样式进行了隐藏
            3.注：使用v-if时，元素可能无法获取到，使用v-show一定可以获取到 -->

      <!-- 列表渲染
          使用详情看v-for.html
          v-for指令：
            1.用于展示列表数据
            2.语法：v-for='(item,index) in xxx' :key='yyy'
            3.可遍历数组、对象、字符串、指定次数
          key的作用：
            1.key是虚拟DOM对象的标识，当状态中的数据发生变化时，Vue会根据新数据生成新的虚拟DOM，随后Vue进行新旧虚拟DOM的比较
            2.新旧虚拟DOM的比较：
                1)旧虚拟DOM中找到了和新虚拟DOM相同的key：
                    1.若虚拟DOM内容没变，则直接使用之前的真实DOM
                    2.若虚拟DOM内容发生改变，则生成新的真实DOM，然后替换页面中之前的真实DOM
                2)旧虚拟DOM中没有找到和新虚拟DOM相同的key：
                    创建新的真实DOM，然后渲染到页面
            3.用index作为key可能引发的问题：
                1)若对数据进行：逆序添加、逆序删除等破坏顺序操作：
                    会产生没有必要的真实DOM更新，虽然页面效果没问题，但是效率低
                2)如果结构中还包括输入类DOM：
                    会产生错误的DOM更新，页面效果就可能出问题
            4.开发中 key的选择：
                1.最好使用每条数据的唯一标识作为key，如id、手机号、身份证号码、学号等唯一值
                2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作，仅用于渲染列表用于展示，使用index作为key是没问题的 -->
    </div>

    <!-- 引入Vue开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <!-- dayjs 日期处理插件 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.7/dayjs.min.js"></script>
    <script>
      // 全局过滤器
      // 截取字符串前四个字符
      Vue.filter("mySlice", function (value) {
        return value.slice(0, 4);
      });

      // 自定义全局指令
      Vue.directive("two", function (e, b) {
        e.innerText = b.value * 20;
      });
      //阻止Vue启动时生成生产提示
      Vue.config.productionTip = false;
      /* 认识Vue：
          1.首先创建一个Vue实例，且要传入一个配置对象el
          2.挂载点容器里的代码符合html规范，只不过混入了一些Vue语法
          3.挂载点容器里的代码称为【Vue模板】
            Vue模板语法有2大类：
              1.插值语法：
                功能：用于解析标签体内容
                写法：{{xxx}}中的xxx写js表达式，会自动读取到data中的所有属性
              2.指令语法：
                功能：用于解析标签（包括标签属性、标签体内容、绑定事件等）
                举例：v-bind:href='xxx',xxx写js表达式,会自动读取到data中的所有属性
                注：指令语法有很多，详见Vue指令文件夹
          4.Vue实例和容器是一一对应的
          5.真实开发中只有一个Vue实例，且会搭配组件使用
          6.一旦data中的数据发生变化，那么页面中用到该数据的也会自动更新

          注：js表达式和js代码（语句）
            1.js表达式：一个表达式会产生一个值，可以放在如何一个需要值的地方
              1) a
              2) a+b
              3) demo(1)
              4) x === y ? 'a' : 'b'
            2.js代码（语句）
              1) if(){}
              2) for(){}*/

      //MVVM模型
      /* MVVM模型
          1.M：模型（Model）：data中的数据
          2.V：视图（View）：模板代码
          3.VM：视图模型（ViewModel）：Vue实例
          特点：
            1.data中的所有属性，最后都出现在了VM上
            2.VM上的所有属性及Vue原型的所有属性，在Vue模板中都可以直接使用 */

      //数据代理：通过一个对象代理对另一个对象中属性的操作（读/写）
      /* Vue中的数据代理:
            通过VM对象来代理data对象中属性的操作（读/写）
         Vue中的数据代理的好处：
            更加方便的操作data中的数据
         基本原理：
            通过Object.defineProperty()把data对象中所有属性添加到VM上
            为每一个添加到VM上的属性，指定一个getter/setter
            在getter/setter内部去操作（读/写）data中对应的属性 */

      // Vue监视数据的原理
      /* 1.Vue会监视data中所有层次的数据
         2.监视对象中的数据：
            通过setter实现监视，且要在new Vue时就传入要监视的数据
              1)对象中后添加的属性，Vue默认不做响应式处理
              2)如需要给后添加的属性做响应式，需要使用API：
                  Vue.set(target,propertyName/index,value)或
                  vm.$set(target,propertyName/index,value)
         3.监视数组中的数据：
            通过包裹数组更新元素的方法实现，本质就是：
              1)调用原生对应的方法对数组进行更新
              2)重新解析模板，然后更新页面
         4.在Vue修改数组中的某个元素一定要用以下方法：
            1)使用API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
            2)Vue.set()或vm.$set()
              注：Vue.set()和vm.$set()不能给vm或vm的根数据对象添加属性*/

      //创建Vue实例
      var vm = new Vue({
        // el:挂载点
        /* 特点：
              1.Vue实例的作用范围是el选中的元素以及其后代元素
              2.el可以使用其他的选择器，但是建议使用ID选择器
              3.el可以使用其他的标签，但是不能使用html和body标签
           写法：
              1. new Vue创建Vue实例时配置，如 el:'#root'
              2. 先创建Vue实例，再通过实例名挂载指定el的值，如 vm.$mount('#root') */

        el: "#root", //el的第一种写法

        // data:数据对象
        //#region
        /* 特点：
              1.data内除了字符串还可以用对象，数组等
           写法：
              1.对象式 如 data:{name:'对象式'}
              2.函数式(推荐) 如 data(){return{name:'函数式'}} 或 data:function(){return{name:'函数式'}}*/

        //data的对象式写法
        /* data: {
          message: "Hello Vue",
          person: {
            id: "1",
            name: "不知道的人",
          },
          city: ["北京", "上海", "广州", "深圳", "杭州"],
        }, */

        //data的函数式写法
        data: function () {
          return {
            message: "Hello Vue",
            person: {
              id: "1",
              name: "不知道的人",
            },
            city: ["北京", "上海", "广州", "深圳", "杭州"],
            //用于绑定class样式
            mood: "bgred",
            classArr: ["addstyle1", "addstyle2", "addstyle3"],
            classObj: {
              bgred: false,
              bgblue: true,
              bggreen: false,
            },
            // 用于绑定style样式
            styleObj: {
              fontSize: "40px",
              color: "red",
            },
            styleArr: [
              { fontSize: "30px", color: "blue" },
              { fontWeight: "700" },
            ],
            // 用于计算属性
            firstName: "张",
            lastName: "三",
            // 用于Vue监视数据
            student: {
              name: "bell",
              age: 22,
              hobby: ["吃饭", "睡觉", "打豆豆"],
              friends: [
                { name: "tom", age: 18 },
                { name: "jerry", age: 20 },
              ],
            },
            //用于过滤器
            time: Date.now(),
            //用于自定义指令
            myNum: 1,
            // 用于生命周期
            opacity: 1,
          };
        },
        //#endregion

        //事件
        //#region
        //使用详情看v-on.html
        /* 事件的基本使用：
          1.使用 v-on:xxx 或 @xxx 绑定事件，xxx是事件名
          2.事件的回调需要配置在methods对象中，最终会在VM上
          3.methods中配置的函数，不要用箭头函数，否则this就不是VM了
          4.methods中配置的函数，都是Vue所管理的函数，this的指向是VM或组件实例对象
          5.@click='demo' 和 @click='demo($event)' 效果一致，但后者可以传参的同时返回event
          6.绑定事件时 @xxx='yyy' yyy可以写一些简单的语句*/
        /* Vue中的事件修饰符：
          1.prevent 阻止默认事件
          2.stop 阻止事件冒泡
          3.once 事件只触发一次
          4.capture 事件捕获
          5.self 只有event.target是当前操作元素时触发事件
          6.passive 事件的默认行为立即执行，无需等待事件回调执行完毕
          7.修饰符可以连着写，如 @click.prevent.stop */
        /* 键盘事件：
            1.Vue中常用的按键别名：
                回车：enter
                删除：delete
                退出：esc
                空格：space
                换行：tab(需要配合keydown使用)
                上：up
                下：down
                左：left
                右：right
            2.Vue中未提供别名的按键，可以使用按键原始的key值去绑定，但要转为caps-lock（转换大小写）
            3.系统修饰符：ctrl、alt、shift、meta
                1）配合keyup使用：按下修饰符的同时，再按下其他按键，随后释放其他按键，事件才触发
                2）配合keydown使用：正常触发
                3)在这之后可以加别的按键,如 @keyup.ctrl.y 按下ctrl和y触发
            4.用keyCode指定按键（不推荐）
            5.Vue.config.keyCodes.自定义按键名 = 键码 可以定制按键别名（不推荐） */
        methods: {
          addId() {
            this.person.id++;
          },
          changeMood() {
            this.mood = "bgblue";
          },

          // 用于Vue监视数据
          addSex() {
            // Vue.set(this.student, "sex", "男");
            this.$set(this.student, "sex", "男");
          },
          addFriends() {
            this.student.friends.unshift({ name: "jack", age: 22 });
          },
          updateFirstFriendName() {
            this.student.friends[0].name = "张三";
          },
          addHobby() {
            this.student.hobby.push("学习");
          },
          updateHobby() {
            // Vue.set(this.student.hobby, 0, "开车");
            this.$set(this.student.hobby, 0, "开车");
          },
          removeBad() {
            this.student.hobby = this.student.hobby.filter((h) => {
              return h !== "打豆豆";
            });
          },
          //用于生命周期
          destoryVm() {
            this.$destroy();
          },
        },
        //#endregion

        //计算属性
        //#region
        /* 计算属性：
        1.定义：要用的属性不存在，要通过已有的属性计算得来
        2.原理：底层借助了Object.defineproperty方法提供的getter和setter
        3.get函数什么时候执行
            1)初次读取时会执行一次
            2)当依赖的数据发生改变时会被再次调用
        4.优点：与methods实现相比，内部有缓存机制
        5.注：
            1)计算属性最终会出现在VM上，可以直接读取使用
            2)如果计算属性要修改，必须写set函数去响应修改，且set中要使计算时依赖的数据发生改变 */
        computed: {
          fullName: {
            get() {
              return this.firstName + "-" + this.lastName;
            },
            set(value) {
              const arr = value.split("-");
              this.firstName = arr[0];
              this.lastName = arr[1];
            },
          },
          // 计算属性简写 只有getter时才能使用简写
          /* fullName(){
            return this.firstName + "-" + this.lastName;
          } */
        },
        //#endregion

        // 侦听属性
        //#region
        /* 侦听属性：
            1.当被侦听的属性变化时，回调函数自动调用，进行相关操作
            2.侦听的属性必须存在才能进行侦听
            3.侦听的两种写法：
                1) new Vue时传入watch配置
                2) 通过vm.$watch侦听
            4.侦听单级结构中属性变化时属性可以不加引号,如 watch:{message:{}}
            5.侦听多级结构中单个属性变化时属性要加引号，如 watch:{'person.id':{}}
            6.如果要侦听多级结构中所有属性的变化，就要使用深度侦听 deep:true

           深度侦听：
            1.Vue中的watch默认不侦听对象内部值的改变（一层）
            2.配置deep:true可以侦听对象内部值改变（多层）
            注：
              1.Vue自身可以侦听对象内部值的改变，但Vue提供的watch默认不可以
              2.使用watch时根据数据的具体结构，决定是否使用深度侦听

           侦听属性简写：
              要求只使用handler时，即没有immediate和deep属性修改时才可以简写
        */

        //第一种侦听写法
        watch: {
          // 正常写法
          //侦听多级结构中单个属性的变化
          "person.id": {
            //初始化时调用handler
            immediate: true,
            //handler在被侦听属性发生改变时调用
            handler(newValue, oldValue) {
              console.log("ID被修改了", newValue, oldValue);
            },
          },
          //侦听多级结构中所有属性的变化
          /* 计算属性和侦听属性的区别：
            1.计算属性可以实现的功能，侦听属性也可以实现
            2.侦听属性可以实现的功能，计算属性不一定能实现，如侦听属性可以异步操作
           注：
            1.所有被Vue管理的函数，最好写普通函数，这样this的指向才是组件实例对象
            2.所有不被Vue管理的函数，（如定时器的回调函数，ajax的回调函数，promise的回调函数），最好写箭头函数，这样this的指向才是组件实例对象 */
          person: {
            deep: true,
            handler() {
              setTimeout(() => {
                console.log("person改变了");
              }, 1000);
            },
          },

          //简写
          /* "person.id"(newValue, oldValue) {
            console.log("ID被修改了", newValue, oldValue);
          }, */
        },
        //#endregion

        //过滤器
        //#region
        /* 定义：对要显示的数据进行特定格式化后再显示（适用于逻辑简单的处理）
           语法：
              1.注册过滤器：Vue.filter(name,callback) 或 new Vue{filters:{}}
              2.使用过滤器：{{xxx | 过滤器名}} 或 v-bind:属性 = 'xxx | 过滤器名'
           注：
              1.过滤器可以接收额外参数，多个过滤器可以串联
              2.并没有改变原本的数据，是产生新的对应的数据 */

        // 局部过滤器
        filters: {
          // 格式化日期
          timeFormater(value, str = "YYYY-MM-DD HH:mm:ss") {
            return dayjs(value).format(str);
          },
        },
        //#endregion

        //自定义指令
        //#region
        /* 语法：
              1.局部指令：
                new Vue({
                  directives:{指令名：配置对象，指令名：回调函数}
                })
              2.全局指令：
                Vue.directive(指令名：配置对象) 或 Vue.directive(指令名：回调函数)
           配置对象中常用的三个回调：
              1.bind：指令与元素成功绑定时调用
              2.inserted：指令所在元素被插入页面时调用
              3.update：指令所在的模板被重新解析时调用
           注：
              1.指令定义时不加v-，调用时要加v-
              2.指令名如果是多个单词，不要用驼峰命名法，用 - 隔开单词 kebab-case命名法 */
        // 自定义局部指令
        directives: {
          // 自定义指令第一个参数为绑定的真实DOM元素,第二个参数为绑定对象

          // 函数式写法
          // 自定义big函数，实现功能和v-text类似，但会把绑定的数值放大10倍
          // 函数式写法会在 1.指令与元素成功绑定时调用 2.指令所在的模板被重新解析时调用
          big(element, binding) {
            // console.log(binding);
            element.innerText = binding.value * 10;
          },

          // 对象式写法
          "focus-on": {
            // 指令与元素成功绑定时调用
            bind(e, b) {
              e.value = b.value;
            },
            // 指令所在元素被插入页面时调用
            inserted(e, b) {
              e.focus();
            },
            // 指令所在的模板被重新解析时调用
            update(e, b) {
              e.value = b.value;
            },
          },
        },
        //#endregion

        // 生命周期
        //#region
        /*  生命周期：
               1.又称生命周期回调函数、生命周期函数、生命周期钩子
               2.是Vue在关键时刻调用的一些特殊名称的函数
               3.生命周期函数的名字不可更改，但函数的具体内容可以根据需求改变
               4.生命周期函数中的this指向vm或组件实例对象
            生命周期函数：
               1.beforeCreate：在将要创建vm时调用
               2.created：在创建完毕vm时调用
               3.beforeMount：在将要挂载入页面时调用
               4.mounted：在挂载完毕时调用
               5.beforeUpdate：在将要更新时调用
               6.updated：在更新完毕时调用
               7.beforeDestory：在将要销毁vm时调用
               8.destroyed：在销毁完毕时调用
            常用的生命周期钩子：
               1.mounted：发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
               2.beforeDestroy：清除定时器、接绑自定义事件、取消订阅消息等收尾工作
            销毁Vue实例：
               1.销毁后借助Vue开发者工具看不到任何信息
               2.销毁后自定义事件会失效，但原生DOM事件依然有效
               3.一般不会在beforeDestroy操作数据，因为即使操作数据也不会触发更新流程 */
        // 挂载完毕时调用
        mounted() {
          console.log("mounted", this);
          this.timer = setInterval(() => {
            this.opacity -= 0.01;
            if (this.opacity <= 0) this.opacity = 1;
          }, 16);
        },
        // 将要销毁时调用
        beforeDestroy() {
          console.log("vm将销毁");
          clearInterval(this.timer);
        },
        //#endregion
      });
      // vm.$mount("#root"); //el的第二种写法

      //第二种侦听写法
      //正常写法
      /* vm.$watch("person.id", {
        immediate: true,
        handler(newValue, oldValue) {
          console.log("ID被修改了", newValue, oldValue);
        },
      }); */

      //简写
      /* vm.$watch("person.id", function (newValue, oldValue) {
        console.log("ID被修改了", newValue, oldValue);
      }); */
    </script>
  </body>
</html>
